Object-oriented languages and the computer systems upon which they are implemented generally provide two different paradigms for adding functionality to an existing object. According to the first paradigm, the object-oriented language itself is dynamic. That is, the language is defined such that new methods (functions) can be added to objects on the fly, even after compile time. An example of this kind of object-oriented language is Objective-C. In Objective-C, each method call causes a message to be sent to the object; thus, a new method can be added to an object without requiring recompilation of the object and relinking other objects that desire to invoke the added method. Specifically, in a message-based system, in order for an object to send a message to invoke the new method, the object need only know the identifier of the target object and the name of the new method. However, because each method call involves message passing overhead, such a system is flexible at the cost of being slow.
According to the second paradigm, the object-oriented language provides more static objects, whose functionality is determined at compile time. That is, all of the methods of an object need to be declared at compile time. In objects caused by the earthquake's shock waves based upon the height of each object. The programmer may later create a specific algorithm for simulating how the motion effects differ when objects are located on different types of soil. If the programmer can replace an existing method implementation in the simulation by a new implementation of the same method name (e.g., a "motion" method), then some changes can be made to the model. If, however, the programmer does not want to replace the behavior simulated by the general algorithm but wants to add the soil motion algorithm so that the effects of differing soil can be controlled simultaneously and independently, then such changes are more difficult because the new method (not previously declared) cannot be added easily in a static environment.
The present invention is described below using some object-oriented techniques; thus, an overview of well-known object-oriented programming techniques is provided. Two common characteristics of object-oriented programming languages are support for data encapsulation and data type inheritance. Data encapsulation refers to the binding of functions and data. Inheritance refers to the ability to declare a data type in terms of other data types. In the C++ language, data encapsulation and inheritance are supported through the use of classes. A class is a user-defined type. A class declaration describes the data members and function members of the class. A function member is also referred to as a method of a class. The data members and function members of a class are bound together in that the function operates on an instance of the class. An instance of a class is also called an object of the class. Thus, a class provides a definition for a group of objects with similar properties and common behavior.
To allocate storage for an object of a particular type (class), an object is instantiated. Once instantiated, data can be assigned to the data members of the particular object. Also, once instantiated, the function members of the particular object can be invoked to access and manipulate the data these systems, in order to add new behavior, the declaration for the object must be recompiled. The Component Object Model ("COM") of objects in the OLE 2.01 environment, established by Microsoft Corporation in Redmond, Wash., is an example of the latter paradigm. According to the COM paradigm, each object declares its methods at compile time. Although new implementations of existing methods can be loaded at run time (as long as the method declaration remains constant), new methods cannot be added after compile time. Objects that abide by the COM paradigm are hereafter referred to as "COM objects."
Computer games and other types of simulation environments pose problems for object-oriented systems that support a static environment paradigm, such as OLE 2.01. For example, in an adventure game, a player may search for treasures in a maze and meet up with characters (e.g., an enemy soldier) and other things (e.g., a boulder) that help or hinder the player's progress. Often these characters and other things acquire different behaviors and capabilities based upon certain triggered events or properties. For example, the player might acquire and drink a magic potion, which suddenly enables the player to fly through the maze. These simulation environments typically represent each character or thing as an object stored in memory. Thus, an implementation of such a game would need to dynamically provide the player with flying capabilities after the player object has been instantiated. This behavior is difficult to achieve in an object-oriented environment that only supports the static declaration of object methods.
Similarly, an extended duration simulation project may require changes to the underlying model over time. For example, in a simulation of the effects of an earthquake on a nuclear power plant, a programmer may want to vary the types of motion caused by the earthquake. Specifically, the simulation program may be executing a general algorithm for determining the motion of members. Thus, in this manner, the function members implement the behavior of the object, and the object provides a structure for encapsulating data and behavior into a single entity.
To support the concept of inheritance, classes may be derived from (based upon the declaration of) other classes. A derived class is a class that inherits the characteristics--data members and function members--of its base classes. A class that inherits the characteristics of another class is a derived class. A class that does not inherit the characteristics of another class is a primary (root) class. A class whose characteristics are inherited by another class is a base class. A derived class may inherit the characteristics of several classes; that is, a derived class may have several base classes. This is referred to as multiple inheritance.
A class may also specify whether its function members are virtual. Declaring that a function member is virtual means that the function can be overridden by a function of the same name and type in a derived class. If a virtual function is declared without providing an implementation, then it is referred to as a pure virtual function. A pure virtual function is a virtual function declared with the pure specifier, "=0". If a class specifies a pure virtual function, then any derived class needs to specify an implementation for that function member before that function member may be invoked. A class which contains at least one pure virtual function member is an abstract class.
FIG. 1 is a block diagram illustrating typical data structures used to represent an object. An object is composed of instance data (data members) and function members, which implement the behavior of the object. The data structures used to represent an object comprise instance data structure 101, virtual function table 102, and the function members 103, 104, 105. The instance data structure 101 contains a pointer to the virtual function table 102 and contains data members. The virtual function table 102 contains an entry for each virtual function member defined for the object. Each entry contains a reference to the code that implements the corresponding function member. The layout of this sample object conforms to models described in U.S. Pat. No. 5,297,284, entitled "A Method for Implementing Virtual Functions and Virtual Bases in a Compiler for an Object Oriented Programming Language," which is hereby incorporated by reference. In the following, an object will be described as an instance of a class as defined by the C++ programming language. One skilled in the art would appreciate that other object models can be defined using other programming languages.
An advantage of using object-oriented techniques is that these techniques can be used to facilitate the sharing of objects. For example, a program implementing the function members of an instantiated object (a "server program") can share the object with another program (a "client program"). To allow an object of an arbitrary class to be shared with a client program, interfaces are defined through which an object can be accessed without the need for the client program to have access to the class definitions at compile time. An interface is a named set of logically related function members. In C++, an interface is an abstract class with no data members and whose virtual functions are all pure. Thus, an interface provides a published protocol for two programs to communicate. Interfaces are typically used for derivation: a program defines (implements) classes that provide implementations for the interfaces the classes are derived from. Thereafter, objects are created as instances of these derived classes. Objects instantiated from a derived class implementing particular interfaces are said to "support" the interfaces. An object supports one or more interfaces depending upon the desired functionality.
When a client program desires to share an object, the client program needs access to the code that implements the interfaces for the object (the derived class code). In the OLE 2.01 environment, to access the derived class code (also referred to as class code), each class implementation is given a unique class identifier (a "CLSID"). For example, code implementing a spreadsheet object developed by Microsoft Corporation may have a class identifier of "MSSpreadsheet," while code implementing a spreadsheet object developed by another corporation may have a class identifier of "LTSSpreadsheet." A persistent registry in each computer system is maintained that maps each CLSID to the code that implements the class. Typically, when a spreadsheet program is installed on a computer system, the persistent registry is updated to reflect the availability of that class of spreadsheet objects. So long as a spreadsheet developer implements each function member defined by the interfaces to be supported by spreadsheet objects and so long as the persistent registry is maintained, the client program can access the function members of shared spreadsheet objects without regard to which server program has implemented them or how they have been implemented.
Since an object may support some interfaces and not others, a client program may need to determine at runtime whether a particular object supports a particular interface. To enable this determination, every COM object supports the IUnknown interface, which contains a function member, QueryInterface, that indicates which interfaces are implemented for the object. The QueryInterface method is defined as follows:
virtual HRESULT QueryInterface (REFIID iid, void**ppv)=0;
The QueryInterface method is passed an interface identifier in parameter iid (of type REFIID) and returns in parameter ppv a pointer to the implementation of the designated interface "iid." Thus, the QueryInterface method is typically coded to know about all available interfaces of the object to which it belongs. If the object does not support the interface, then the QueryInterface method returns false. The type HRESULT indicates a predefined status.
The IUnknown interface also defines the methods AddRef and Release, which are used to implement reference counting. Whenever a new reference to an interface is created, the AddRef method is invoked to increment a reference count of the object. When a reference is no longer needed, the Release method is invoked to decrement the reference count of the object and, when the reference count goes to zero, the object is deallocated.
FIG. 2 is a symbolic representation of an object. In the following description, an object data structure is represented by the shape 201 labeled with the interfaces through which the object may be accessed. As shown, object 201 supports IInterfacel and IInterface2.